package com.feigncommon;

import com.common.global.R;
import feign.hystrix.FallbackFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;

import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.Arrays;
import java.util.List;

/**
 * Feign api fallback factory
 *
 * @description: 通用fallback
 * @author: cyj
 * @date 2022 -04-29 11:42:06
 *
 *
 */
@Slf4j
public abstract class FeignApiFallbackFactory<T> implements FallbackFactory<T> {

    /**
     * requestAnnos
     */
    public static final List<Class<? extends Annotation>> requestAnnos
            = Arrays.asList(GetMapping.class,
            PostMapping.class,
            PutMapping.class,
            DeleteMapping.class,
            PatchMapping.class,
            RequestMapping.class);

    /**
     * T clazz
     */
    protected final Class<? extends T> tClazz;

    /**
     * T
     */
    protected T t;

    /**
     * Feign api fallback factory
     */
    public FeignApiFallbackFactory() {
        this.tClazz = this.getApiTypeClass();
    }

    /**
     * Feign api fallback factory
     *
     * @param t t
     */
    public FeignApiFallbackFactory(T t) {
        this.tClazz = this.getApiTypeClass();
        this.t = t;
    }

    /**
     * Gets api type class *
     *
     * @return the api type class
     */
    private Class<? extends T> getApiTypeClass() {

        Class<? extends T> typeClass = this.getTypeClass();
        if (!typeClass.isAnnotationPresent(FeignClient.class)) {
            throw new RuntimeException(String.format("%s 的泛型 %s 不是一个 FeignClient", this.getClass().getName(), typeClass.getName()));
        }
        return typeClass;
    }

    /**
     * Gets type class *
     *
     * @return the type class
     */
    private Class<? extends T> getTypeClass() {
        return (Class<? extends T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
    }

    /**
     * Gets invocation handler *
     *
     * @param cause cause
     * @return the invocation handler
     */
    protected InvocationHandler getInvocationHandler(Throwable cause) {
        return new InvocationHandler() {

            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                log.error("---------------------fegin fallback proxy--------------: {}.{}", tClazz.getName(), method.getName());
                log.error(cause.getMessage(), cause);
                Annotation[] annotations = method.getAnnotations();

                boolean fallback = false;

                for (Annotation annotation:
                        annotations) {
                    if (FeignApiFallbackFactory.requestAnnos.contains(annotation.annotationType())) {
                        fallback = true;
                        break;
                    }
                }

                if (fallback) {
                    return R.error("0.0.0000", String.format("calss:%s, method:%s(fallback!)", tClazz.getName(), method.getName()), null);
                } else {
                    return null;
                }
            }
        };
    }

    @Override
    public T create(Throwable cause) {
        return (T) Proxy.newProxyInstance(this.tClazz.getClassLoader(), new Class[]{this.tClazz}, getInvocationHandler(cause));
    }
}
